home *** CD-ROM | disk | FTP | other *** search
/ Amiga Collections: Taifun / Taifun 129 (1990-02-15)(Ossowski, Stefan)(DE)(PD).zip / Taifun 129 (1990-02-15)(Ossowski, Stefan)(DE)(PD).adf / MRPrint / MRPrint.c < prev    next >
C/C++ Source or Header  |  1989-12-25  |  23KB  |  829 lines

  1. /* :ts=4 */
  2. /*
  3.  * MRPrint:     detabbing text file printer for the Amiga 
  4.  * Author:      Mark Rinfret (Usenet: mrr@amanpt1.ZONE1.COM; Bix: markr)
  5.  * 
  6.  * I am offering this to the Amiga user community without restrictions. If you
  7.  * make improvements, please re-release with source.      Enjoy!
  8.  * 
  9.  * 
  10.  * This program will print text files containing embedded tabs and form feeds.
  11.  * Though the default tab setting is 4, the user may override this to some
  12.  * other value as necessary.  MRPrint will also optionally output a page
  13.  * header containing the filename, current date and time, line number and
  14.  * page number.  MRPrint supports variable margins and will enforce them.
  15.  * Line numbers will be printed if requested.  Note that by default, MRPrint
  16.  * prints to PRT:.  If you wish to redirect output, be sure to use the "-s"
  17.  * option.
  18.  * 
  19.  * Usage:  pr [-l] [-n#] [-t#] [-h] [file1]...filen]
  20.  *      options:    -h  do not print a page header 
  21.  *                  -l  print with line numbers 
  22.  *                  -L# set left margin to # 
  23.  *                  -n# print # lines per page 
  24.  *                  -R# set right margin to # 
  25.  *                  -s  print to standard output 
  26.  *                  -t# set tab to #spaces (default 4)
  27.  * 
  28.  * Handles ARP wildcarding.
  29.  * 
  30.  * 08/30/89 -MRR- V3.4: Fixed bug in line numbering.
  31.  * 
  32.  * 11/12/88 -MRR- Changed default margins to 1, 80, lines per page to 62.
  33.  * 
  34.  * 08/26/88 -MRR- When MRPrint detected a binary file, it printed a blank page
  35.  * to "commemorate" the event.  Ugh!
  36.  * 
  37.  * 05/13/88 -MRR- Yeah, I know - version 3.0 didn't last very long. I observed
  38.  * the output with the -s option and decided that the single character I/O I
  39.  * was doing was very unacceptable.  This version buffers both input and
  40.  * output.
  41.  * 
  42.  * 05/12/88 -MRR- THIS PROGRAM HAS BEEN ARPIFIED!  What the hell, I've been
  43.  * wanting to dig into ARP for quite a while.  Now that I have V1.1 of ARP,
  44.  * V3.6 of Manx and a day off, this was as good a program as any to do some
  45.  * exploring.
  46.  */
  47.  
  48. #define AMIGA
  49. /* #define DEBUG */
  50.  
  51. #include <stdio.h>
  52. #include <ctype.h>
  53. #include <libraries/arpbase.h>
  54. #include <arpfunctions.h>
  55. #include <functions.h>
  56.  
  57. #define VERSION "pr version 3.4, 08/29/89 (requires ARP V1.1 or higher)"
  58.  
  59. #define INBUFSIZE       4096L   /* input buffer size */
  60. #define MAXLINE         256
  61. #define OUTBUFSIZE      2048L   /* output buffer size */
  62. #define yes             1
  63. #define no              0
  64. #define SizeOf(x)       ((ULONG) sizeof(x))
  65.  
  66. /*
  67.  * An extended AnchorPath structure to enable full pathnames to be generated
  68.  * by FindFirst, FindNext.
  69.  */
  70.  
  71. struct UserAnchor {
  72.     struct AnchorPath ua_AP;
  73.     BYTE            moreMem[255];
  74. };
  75.  
  76. char           *FGets();        /* AmigaDOS/ARP compatible version. */
  77. char           *NextFile();
  78. void            PutNumber();
  79. void            PutOneChar();
  80. void            PutString();
  81.  
  82. unsigned        abort;          /* Set by CTRL-C, really unnecessary. */
  83. struct UserAnchor *anchor;      /* Used by FindFirst, FindNext */
  84. struct DateTime *dateAndTime;   /* Go ahead - take a wild guess. */
  85. char            dateStr[20], timeStr[20];
  86. unsigned        doLineNumbers = no;
  87. unsigned        endOfInput;
  88. BPTR            f;              /* The current input file (handle) */
  89. char           *fileName;       /* The name of the input file. */
  90. unsigned        forcePage;      /* Set by \f. */
  91. unsigned        headers = yes;  /* Controls page header generation. */
  92. UBYTE          *inBuf, *inBufPtr;       /* Input buffer, sliding pointer */
  93. unsigned        inBufCount, inBufLength;
  94. unsigned        leftMargin = 1;
  95. unsigned        lineNumber;
  96. unsigned        linesPerPage = 60;
  97. UBYTE          *outBuf, *outBufPtr;     /* Output buffer, sliding pointer */
  98. unsigned        outBufLength;   /* Length of output buffer. */
  99. unsigned        pageNumber;
  100. BPTR            printer;        /* Output device/file handle. */
  101. static char    *prtname = "PRT:";
  102. LONG            result;         /* Result of wildcard processing. */
  103. unsigned        rightMargin = 80;
  104. unsigned        srcLine;        /* Current source file line number. */
  105. unsigned        tabSpace = 4;   /* How many spaces 1 tab equals. */
  106. unsigned        tabStops[MAXLINE];      /* Computed tab stops. */
  107. unsigned        useRequester = no;      /* Get filenames with requester? */
  108. unsigned        useStdOut = no; /* Print to standard output? */
  109. unsigned        xargc;          /* arg count after option processing */
  110. char          **xargv;          /* arg vector after option processing */
  111.  
  112.  
  113.  
  114.  
  115. /*
  116.  * This is where all goodness begins.  Actually, I'm not too happy with the
  117.  * size of the main program.  It ought to be broken up (or down :-).
  118.  */
  119. main(argc, argv)
  120.     int             argc;
  121.     char           *argv[];
  122. {
  123.     unsigned        i;
  124.     char           *s;
  125.  
  126.     if (argc) {                 /* zero if started from workbench */
  127.         ++argv;                 /* skip over program name arg */
  128.         --argc;
  129.  
  130.         /* ..process switches.. */
  131.         for (; *(s = *(argv)) == '-'; ++argv, --argc) {
  132.             while (*++s)
  133.                 switch (*s) {
  134.                 case '?':
  135.                     Usage();
  136.  
  137.                 case 'l':
  138.                     doLineNumbers = yes;
  139.                     break;
  140.                 case 'L':
  141.                     if ((leftMargin = Atol(s + 1)) <= 0) {
  142.                         Abort("Bad left margin ", (long) leftMargin);
  143.                     }
  144.                     goto next_arg;      /* Oh my gawd!  A GOTO! */
  145.                 case 'n':
  146.                     linesPerPage = Atol(s + 1);
  147.                     goto next_arg;      /* Oh no!  A nuther one! */
  148.                     break;
  149.                 case 'R':
  150.                     if ((rightMargin = Atol(s + 1)) <= 0 ||
  151.                         rightMargin > MAXLINE) {
  152.                         Abort("Bad right margin ", (long) rightMargin);
  153.                     }
  154.                     goto next_arg;      /* It's a bloody epidemic! */
  155.                 case 's':
  156.                     useStdOut = yes;
  157.                     break;
  158.                 case 't':
  159.                     if ((tabSpace = Atol(s + 1)) <= 0) {
  160.                         Abort("Bad tab specification ", (long) tabSpace);
  161.                     }
  162.                     goto next_arg;      /* This is disgusting! */
  163.                 case 'h':
  164.                     headers = no;
  165.                     break;
  166.                 case 'v':
  167.                     Printf("\n%s\n", VERSION);
  168.                     break;
  169.                 default:
  170.                     Usage();
  171.                 }
  172.             /* Gag!  A label! There must be some goto's sneakin' around... */
  173.     next_arg:;
  174.         }
  175.     }
  176.     /* Check a few argument combinations. */
  177.  
  178.     if (leftMargin >= rightMargin) {
  179.         Abort("Left margin >= right margin?  Ha ha!", 0L);
  180.     }
  181.     if (doLineNumbers)
  182.         leftMargin = 5;         /* No margins with numbering but numbers use
  183.                                  * 5 columns. */
  184.  
  185.  
  186.     SetTabs();                  /* Initialize tab settings. */
  187.  
  188.     /* Allocate input and output buffers. */
  189.  
  190.     inBuf = ArpAlloc(INBUFSIZE);
  191.     if (inBuf == NULL)
  192.         Abort("No memory for input buffer!", INBUFSIZE);
  193.  
  194.     outBuf = ArpAlloc(OUTBUFSIZE);
  195.     if (outBuf == NULL)
  196.         Abort("No memory for output buffer!", OUTBUFSIZE);
  197.  
  198.     /* Get the date and time; we might need it. */
  199.  
  200.     dateAndTime = (struct DateTime *) ArpAlloc(SizeOf(*dateAndTime));
  201.     if (dateAndTime == NULL) {
  202.         Abort("No memory!", SizeOf(*dateAndTime));
  203.     }
  204.     DateStamp(dateAndTime);
  205.     dateAndTime->dat_Format = FORMAT_USA;
  206.     dateAndTime->dat_StrDate = dateStr;
  207.     dateAndTime->dat_StrTime = timeStr;
  208.     StamptoStr(dateAndTime);
  209.  
  210.     if (useStdOut)
  211.         printer = (BPTR) Output();
  212.     else if ((printer = ArpOpen(prtname, MODE_NEWFILE)) == NULL) {
  213.         Abort("Failed to open printer ", IoErr());
  214.     }
  215.     /* Process files. */
  216.  
  217.     xargv = argv;
  218.     if ((xargc = argc) == 0)    /* If no filename args, use requester. */
  219.         useRequester = yes;
  220.     else {
  221.         if ((anchor = (struct UserAnchor *)
  222.              ArpAlloc(SizeOf(*anchor))) == NULL) {
  223.             Abort("No memory!", SizeOf(*anchor));
  224.         }
  225.         anchor->ua_AP.ap_Length = 255;  /* Want full path built. */
  226.         anchor->ua_AP.ap_BreakBits |=
  227.             (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
  228.  
  229.         result = ERROR_NO_MORE_ENTRIES;
  230.     }
  231.  
  232.     while (!abort && (fileName = NextFile())) {
  233.         if ((f = (BPTR) Open(fileName, MODE_OLDFILE)) != NULL) {
  234.             PrintFile();
  235.             Close(f);
  236.             f = NULL;
  237.         } else
  238.             Printf("\n*** MRPrint: Can't open %s for printing ***\n",
  239.                    fileName);
  240.     }
  241. }
  242.  
  243. /*
  244.  *  Abort the program. 
  245.  *  Called with: 
  246.  *      desc:           descriptive text 
  247.  *      code:           error code (printed if non-zero) 
  248.  *
  249.  *  Returns: 
  250.  *      to the system, where else?!
  251.  */
  252.  
  253. Abort(desc, code)
  254.     char           *desc;
  255.     long            code;
  256. {
  257.     Printf("\n*** MRPrint aborting: %s", desc);
  258.     if (code)
  259.         Printf(" (%ld) ", code);
  260.     Puts(" ***");
  261.     if (f)
  262.         Close(f);               /* File open? Close it. */
  263.     ArpExit(20L, 0L);
  264. }
  265.  
  266.  
  267. /* Print one file. */
  268.  
  269. PrintFile()
  270. {
  271.     char            line[MAXLINE];
  272.  
  273.     forcePage = pageNumber = srcLine = 0;
  274.  
  275.     lineNumber = linesPerPage;
  276.  
  277.     inBufPtr = inBuf;
  278.     inBufLength = 0;
  279.     inBufCount = 0;
  280.     outBufPtr = outBuf;
  281.     outBufLength = 0;
  282.     endOfInput = no;
  283.  
  284.     while (FGets(line, MAXLINE - 1, f) != NULL && !abort) {
  285.         ++srcLine;              /* count input lines */
  286.  
  287.         /*
  288.          * Note that top-of-form detection was a rather kludgy addition.  It
  289.          * only works if the first character in the line is a ^L.
  290.          */
  291.         if (*line == '\f') {
  292.             *line = ' ';        /* replace embedded ^L with blank */
  293.             lineNumber = linesPerPage;  /* force new page */
  294.         }
  295.         if (lineNumber >= linesPerPage)
  296.             Header();
  297.         DeTab(line);            /* ..output detabbed line.. */
  298.  
  299.     }
  300.  
  301.     if (srcLine) {              /* We printed something? */
  302.         PutOneChar('\f');       /* ..form-feed after last page.. */
  303.         FlushBuffer();
  304.     }
  305. }
  306.  
  307. /*
  308.  * An attempt has been made to print a line past the right margin. Crash the
  309.  * user's system and melt his...naw, force a new line and output a new left
  310.  * margin.  Also, if the page line count has been exceeded, start a new page.
  311.  */
  312.  
  313. BreakLine()
  314. {
  315.     PutOneChar('\n');
  316.     if (++lineNumber > linesPerPage)
  317.         Header();
  318.     DoLeftMargin();
  319. }
  320.  
  321. /*
  322.  * Output a dashed line according to an obscure algorithm derived through
  323.  * intense empirical analysis while listening to the tune
  324.  * 
  325.  * "Camptown ladies sing this song, DoDash, DoDash..."
  326.  */
  327.  
  328. DoDash()
  329. {
  330.     PutMany(' ', leftMargin);
  331.     PutMany('-', rightMargin - leftMargin - 5);
  332.     PutOneChar('\n');
  333. }
  334.  
  335. /*
  336.  * Output spaces for the left margin, or a source line number, whatever
  337.  * tickles the user's fanny....fancy!
  338.  */
  339.  
  340. DoLeftMargin()
  341. {
  342.     unsigned        i;
  343.  
  344.     if (doLineNumbers) {
  345.         PutNumber(srcLine, 4);
  346.         PutOneChar(' ');
  347.     } else
  348.         PutMany(' ', leftMargin);
  349. }
  350.  
  351. /*
  352.  * Print a header.
  353.  */
  354.  
  355. Header()
  356. {
  357.     int             i;
  358.  
  359.     if (++pageNumber != 1) {
  360.         PutOneChar('\f');       /* Eject if not first page. */
  361.         PutOneChar('\n');
  362.     }
  363.     if (headers) {
  364.         DoDash();
  365.  
  366.         /*
  367.          * Note: there's room for improvement here.  A fancier algorithm
  368.          * would attempt to distribute this information evenly over the
  369.          * current page width.  A less lazy programmer would have written the
  370.          * fancier algorithm.
  371.          */
  372.         PutString("     ");     /* Don't call DoLeftMargin! */
  373.         PutString(fileName);
  374.         PutMany(' ', 2);
  375.         PutString(dateStr);
  376.         PutMany(' ', 2);
  377.         PutString(timeStr);
  378.         PutString("  Page ");
  379.         PutNumber(pageNumber, 0);
  380.         PutString("  Line ");
  381.         PutNumber(srcLine, 0);
  382.         PutOneChar('\n');
  383.  
  384.         DoDash();
  385.  
  386.         PutString("\n");
  387.     }
  388.     lineNumber = 0;
  389. }
  390.  
  391.  
  392. /*
  393.  * Replace embedded tab characters with the appropriate number of spaces,
  394.  * outputting the results to the output device/file. 
  395.  *
  396.  * Called with: 
  397.  *      line: string on which to do replacements 
  398.  *
  399.  * Returns: 
  400.  *      eventually :-)
  401.  */
  402.  
  403. DeTab(line)                     /* DeTab is not as good as DePepsi. */
  404.     char           *line;
  405. {
  406.     int             eol = 0, i, col;
  407.  
  408.     DoLeftMargin();
  409.     col = leftMargin;
  410.  
  411.     /*
  412.      * Note: line[] has a terminating '\n' from fgets()...except if the input
  413.      * line length exceeded MAXLINE.
  414.      */
  415.     for (i = 0; i < strlen(line); ++i)
  416.         if (line[i] == '\t') {  /* ..tab.. */
  417.             do {
  418.                 if (col == rightMargin) {
  419.                     BreakLine();
  420.                     break;
  421.                 }
  422.                 PutOneChar(' ');
  423.                 ++col;
  424.             } while (!tabStops[col]);
  425.         } else if (line[i] == 0x08) {   /* backspace? */
  426.             if (col > 1) {
  427.                 PutOneChar(line[i]);
  428.                 --col;
  429.             }
  430.         } else {
  431.             if (line[i] == '\n')
  432.                 ++eol;
  433.             else if (col == rightMargin)
  434.                 BreakLine();
  435.             PutOneChar(line[i]);
  436.             ++col;
  437.         }
  438.     if (!eol)
  439.         PutOneChar('\n');       /* no end of line? */
  440.     ++lineNumber;
  441. }
  442.  
  443. /* Initialize the tab settings for this file. */
  444.  
  445. SetTabs()
  446. {
  447.     int             i;
  448.  
  449.     for (i = 0; i < MAXLINE; ++i)
  450.         tabStops[i] = (i % tabSpace == 1);
  451. }
  452.  
  453.  
  454. /* Display correct program Usage, then exit. */
  455.  
  456. Usage()
  457. {
  458.     register unsigned i;
  459.     register char  *s;
  460.  
  461.     static char    *usageText[] = {
  462.         "Usage:  pr [-l] [-n#] [-t#] [-h] [-v] [file1] file2] ...",
  463.         "\toptions:",
  464.         "\t\t-h      do not print page headers",
  465.         "\t\t-l      print with line numbers",
  466.         "\t\t-L#     set left margin to #",
  467.         "\t\t-n#     print # lines per page",
  468.         "\t\t-R#     set right margin to #",
  469.         "\t\t-s      print to standard output instead of PRT:",
  470.         "\t\t-t#     set tab to # spaces (default 4)",
  471.         "\t\t-v      display program version number",
  472.         "ARP wildcarding is supported.",
  473.         (char *) NULL           /* last entry MUST be NULL */
  474.     };
  475.  
  476.     for (i = 0; s = usageText[i]; ++i)
  477.         Puts(s);
  478.  
  479.     ArpExit(20L, 0L);
  480. }
  481.  
  482. /*
  483.  * Get the next file name, either from the argument list or via a requester.
  484.  */
  485.  
  486. char *
  487. NextFile()
  488. {
  489. #define NUMBEROFNAMES   10L
  490.  
  491.     static struct FileRequester request;
  492.     static char     dName[DSIZE * NUMBEROFNAMES + 1] = "";
  493.     static char     fName[FCHARS + 1] = "";
  494.  
  495.     struct FileLock *lock;
  496.  
  497.     if (useRequester) {
  498.         if (request.fr_File == NULL) {
  499.             request.fr_File = fName;
  500.  
  501.             /*
  502.              * To get the current directory path, get a lock on it, then use
  503.              * PathName to convert it to a full path.
  504.              */
  505.             lock = Lock("", ACCESS_READ);
  506.             PathName(lock, dName, NUMBEROFNAMES);
  507.             UnLock(lock);
  508.             request.fr_Dir = dName;
  509.             request.fr_Hail = "Select file to print:";
  510.         }
  511.         return FileRequest(&request);
  512.     }
  513.     /*
  514.      * Note: result is initialized to ERROR_NO_MORE_ENTRIES prior to calling
  515.      * this routine for the first time.
  516.      */
  517.  
  518.     while ((result == 0) || (result == ERROR_NO_MORE_ENTRIES)) {
  519.  
  520.         if (result == 0) {      /* Working a pattern? */
  521.             if ((result = FindNext(anchor)) == 0L) {
  522.                 if (SkipDirEntry(anchor))
  523.                     continue;
  524.                 break;
  525.             }
  526.         }
  527.         if (result == ERROR_NO_MORE_ENTRIES) {
  528.             if (xargc <= 0) {
  529.                 result = -1;
  530.                 break;
  531.             }
  532.             result = FindFirst(*xargv, anchor);
  533.             ++xargv;            /* Advance arg list pointer. */
  534.             --xargc;            /* One less arg to process. */
  535.             if (result == 0) {
  536.                 if (SkipDirEntry(anchor))
  537.                     continue;
  538.                 break;
  539.             }
  540.         }
  541.         /* Only one error code is acceptable: */
  542.  
  543.         if (result && (result != ERROR_NO_MORE_ENTRIES)) {
  544.             Printf("\n*** MRPrint I/O error %ld on pattern %s ***\n",
  545.                    result, *xargv);
  546.             result = 0;         /* Allow another pass. */
  547.         }
  548.     }
  549.  
  550.     /* Return filename or NULL, depending upon result. */
  551.     return (result == 0 ? (char *) &anchor->ua_AP.ap_Buf : NULL);
  552. }
  553.  
  554. /*
  555.  * Read one line (including newline) from the input file. 
  556.  * Called with: 
  557.  *      line:       string to receive text 
  558.  *      maxLength:  maximum length of string 
  559.  *      f:          AmigaDOS file handle bee pointer (BPTR, ya' know).
  560.  */
  561.  
  562. char *
  563. FGets(line, maxLength, f)
  564.     char           *line;
  565.     int             maxLength;
  566.     BPTR            f;
  567. {
  568.     char           *buf = line;
  569.     int             c;
  570.     int             lineLength = 0;
  571.  
  572.     if (abort = CheckAbort(NULL)) {
  573.         PutString("\n^C\f");
  574.         Abort("^C", 0L);
  575.     }
  576.     while (lineLength < maxLength) {
  577.         if ((c = GetOneChar(f)) < 0)
  578.             break;
  579.         ++lineLength;
  580.         if ((*buf++ = c) == '\n')
  581.             break;              /* Stop on end of line. */
  582.     }
  583.  
  584.     line[lineLength] = '\0';
  585.  
  586.     if (c < -1) {
  587.  
  588.         /*
  589.          * Report the error to the printer and the console, but don't give up
  590.          * on the rest of the files.  I think they call that being user
  591.          * friendly.
  592.          */
  593.         c = -c;                 /* Invert the error code. */
  594.         Printf("*** I/O error on input %d ***\n", c);
  595.  
  596.         if (!useStdOut) {
  597.             PutString("*** Input I/O error");
  598.             PutNumber(c, 0);
  599.             PutString("***\n");
  600.         }
  601.         lineLength = 0;
  602.     }
  603.     return (lineLength == 0 ? NULL : line);
  604. }
  605.  
  606. /* Flush the printer (output) buffer (phew!). */
  607.  
  608. FlushBuffer()
  609. {
  610.     long            actualLength;
  611.     long            ioResult;
  612.  
  613.     if (outBufLength) {
  614.         actualLength = Write(printer, outBuf, (long) outBufLength);
  615.         if (actualLength != outBufLength) {
  616.             ioResult = IoErr();
  617.             Abort("Output error!", ioResult);
  618.         }
  619.     }
  620.     outBufPtr = outBuf;
  621.     outBufLength = 0;
  622. }
  623.  
  624. /*
  625.  * Get one character from the input stream.  If the input buffer is
  626.  * exhausted, attempt to get some more input.  If this is the first input
  627.  * buffer for this file, check the buffer for binary content. 
  628.  *
  629.  *  Called with: 
  630.  *      f:      input file handle 
  631.  *
  632.  *  Returns: 
  633.  *              character code (>= 0) or status (< 0, -1 => end of input)
  634.  */
  635.  
  636. int
  637. GetOneChar(f)
  638.     BPTR            f;
  639. {
  640.     int             ioStatus;
  641.  
  642.     if (endOfInput)
  643.         return -1;
  644.  
  645.     if (inBufLength <= 0) {
  646.         inBufLength = Read(f, inBuf, INBUFSIZE);
  647.  
  648.         /*
  649.          * If this is the first buffer, test it for binary content. If the
  650.          * file is binary, skip it by setting the actualLength to zero
  651.          * (simulate end of file).
  652.          */
  653.         if ((++inBufCount == 1) && inBufLength > 0) {
  654.             if (SkipBinaryFile(anchor))
  655.                 inBufLength = 0;
  656.         }
  657.         if (inBufLength <= 0) {
  658.             if (inBufLength == -1)
  659.                 ioStatus = -IoErr();
  660.             else {
  661.                 ioStatus = -1;
  662.                 endOfInput = yes;
  663.             }
  664.  
  665.             return ioStatus;
  666.         }
  667.         inBufPtr = inBuf;
  668.     }
  669.     --inBufLength;
  670.     return *inBufPtr++;
  671. }
  672.  
  673. /*
  674.  * Put multiple copies of a character into the output buffer (repeat). 
  675.  *
  676.  *  Called with: 
  677.  *      c:              character to be repeated 
  678.  *      n:              number of copies 
  679.  *
  680.  *  Returns: 
  681.  *                      tired but satisfied
  682.  */
  683.  
  684. PutMany(c, n)
  685.     int             c, n;
  686.  
  687. {
  688.     for (; n > 0; --n)
  689.         PutOneChar(c);
  690. }
  691.  
  692. /*
  693.  * Output a simple formatted unsigned number. 
  694.  *
  695.  * Called with: 
  696.  *      number:     value to be formatted 
  697.  *      length:     number of digits desired (0 => doesn't matter)
  698.  */
  699. void
  700. PutNumber(number, length)
  701.     unsigned        number, length;
  702. {
  703.     unsigned        digitCount = 0, i;
  704.     char            digits[6];
  705.  
  706.     do {
  707.         digits[digitCount++] = (number % 10) + '0';
  708.         number /= 10;
  709.     } while (number);
  710.  
  711.     while (length > digitCount) {
  712.         PutOneChar(' ');
  713.         --length;
  714.     }
  715.  
  716.     do {
  717.         PutOneChar(digits[--digitCount]);
  718.     } while (digitCount);
  719. }
  720.  
  721. /*
  722.  * Output one character to the printer device/file. 
  723.  *
  724.  * Called with: 
  725.  *      c:      character to be output 
  726.  *
  727.  * Returns: 
  728.  *              nada
  729.  */
  730. void
  731. PutOneChar(c)
  732.     int             c;
  733. {
  734.     if (outBufLength >= OUTBUFSIZE)
  735.         FlushBuffer();
  736.  
  737.     *outBufPtr++ = c;
  738.     ++outBufLength;
  739. }
  740.  
  741. /*
  742.  * Output a string to the printer device/file. 
  743.  *
  744.  * Called with: 
  745.  *      s:          string to output 
  746.  *
  747.  * Returns: 
  748.  *                  when it's done, of course!
  749.  */
  750. void
  751. PutString(s)
  752.     char           *s;
  753. {
  754.     register int    c;
  755.     register char  *s1;
  756.  
  757.     for (s1 = s; c = *s1; ++s1)
  758.         PutOneChar(c);
  759. }
  760.  
  761. /*
  762.  * Test the contents of the first buffer for binary data.  If the buffer is
  763.  * determined to have binary content, tell the user that we are skipping the
  764.  * file.  This allows the user to give a single wildcard specification
  765.  * without worrying about printing object, data and program files (assuming,
  766.  * of course, that binary data is detected within the first INBUFSIZE bytes
  767.  * of the file). 
  768.  *
  769.  * Called with: 
  770.  *      anchor:         pointer to UserAnchor structure describing the file 
  771.  
  772.  * Returns: 
  773.  *      yes:            file contains binary 
  774.  *      no:             file is text (we think)
  775.  */
  776.  
  777. int
  778. SkipBinaryFile(anchor)
  779.     struct UserAnchor *anchor;
  780. {
  781.     char           *strchr();
  782.  
  783.     /*
  784.      * The following string describes binary characters that are considered
  785.      * to be "OK".  These are, from left to right:
  786.      * 
  787.      * newline, form feed, tab, carriage return, backspace, ESCape
  788.      * 
  789.      */
  790.     static char    *okSpecial = "\n\f\t\015\010\033";
  791.     register UBYTE  c;
  792.     register int    i;
  793.     int             isBinary = no;
  794.  
  795.     for (i = 0; i < inBufLength; ++i)
  796.         if (((c = inBuf[i]) < ' ') || c > 0x7F) {
  797.             if (!strchr(okSpecial, c)) {
  798.                 isBinary = yes;
  799.                 break;
  800.             }
  801.         }
  802.     if (isBinary) {
  803.         Printf("\n*** MRPrint: skipping binary file %s ***\n", fileName);
  804.     }
  805.     return isBinary;
  806. }
  807.  
  808. /*
  809.  * Test the file described by the anchor parameter for "directoryness". If
  810.  * it's a directory, print a message that we're skipping it. 
  811.  * Called with: 
  812.  *      anchor:         file entry info returned by FindFirst, FindNext 
  813.  *
  814.  * Returns:
  815.  *      yes:            file is a directory 
  816.  *      no:             file is a file (astonishing, eh?)
  817.  */
  818. int
  819. SkipDirEntry(anchor)
  820.     struct UserAnchor *anchor;
  821. {
  822.     if (anchor->ua_AP.ap_Info.fib_DirEntryType >= 0) {
  823.         Printf("\n*** MRPrint: skipping directory %s ***\n",
  824.                &anchor->ua_AP.ap_Buf);
  825.         return yes;
  826.     }
  827.     return no;
  828. }
  829.